package stc.skymobi.netty.handler.codec.access;

import com.google.common.primitives.Bytes;
import com.google.common.primitives.Ints;
import com.skymobi.common.EncryptedUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import stc.skymobi.bean.bytebean.core.BeanFieldCodec;
import stc.skymobi.bean.util.meta.Int2TypeMetainfo;
import stc.skymobi.netty.handler.codec.access.protocol.AccessFixedHeader;
import stc.skymobi.netty.handler.codec.access.protocol.AccessHeaderWithSeqNum;
import stc.skymobi.netty.handler.codec.access.protocol.signal.EsbAccess2TerminalSignal;
import stc.skymobi.util.ByteUtils;

import java.util.Arrays;
import java.util.List;

/**
 * ESB Access到手机终端的协议解码器
 */
public class NettyAccessDecoder extends ByteToMessageDecoder {

    private static final Logger logger = LoggerFactory.getLogger(NettyAccessDecoder.class);

    private BeanFieldCodec esbBeanCodec;
    private Int2TypeMetainfo esbTypeMetainfo;
    private int encryptKey;

    //大于1M的数据包不建议走ESB
    private int maxMessageLength = 1024 * 1024;

    private int dumpBytes = 256;
    private boolean isDebugEnabled;

    @Override
    public void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
        if (buffer.readableBytes() < AccessFixedHeader.FIXED_HEADER_SIZE) {
            return;
        }

        //第5-6字节为包长度
        short packageLength = buffer.getShort(buffer.readerIndex() + 4);
        //第7-8字节为flag
        short flag = buffer.getShort(buffer.readerIndex() + 6);
        if (packageLength > maxMessageLength) {
            byte[] headerBytes = new byte[AccessFixedHeader.FIXED_HEADER_SIZE];
            buffer.readBytes(headerBytes);
            logger.error("header.length {} exceed maxMessageLength {}, "
                            + " so drop this connection.\r\ndump bytes received:\r\n"
                            + ByteUtils.bytesAsHexString(headerBytes, dumpBytes),
                    packageLength, maxMessageLength
            );
            ctx.channel().close();
            return;
        }
        if (buffer.readableBytes() < packageLength) {
            logger.debug("readableBytes {} smaller than packageLength {},waiting for remain bytes", buffer.readableBytes(), packageLength);
            return;
        }

        logger.trace("parse header... try parse...");

        byte[] message = new byte[packageLength];
        buffer.readBytes(message);

        if (encryptKey != 0 && message.length > AccessFixedHeader.FIXED_HEADER_SIZE) {
            byte[] unEncrypted = Arrays.copyOfRange(message, 0, AccessFixedHeader.FIXED_HEADER_SIZE);   //access固定头不加密
            byte[] encrypted = Arrays.copyOfRange(message, AccessFixedHeader.FIXED_HEADER_SIZE, message.length);
            if (logger.isTraceEnabled()) {
                logger.trace("decode encrypted body {} with key {}", ByteUtils.bytesAsHexString(encrypted, 1000), ByteUtils.bytesAsHexString(Ints.toByteArray(encryptKey), 1000));
            }
            encrypted = EncryptedUtils.decode(Ints.toByteArray(encryptKey), encrypted);
            message = Bytes.concat(unEncrypted, encrypted);
        }


        byte[] headerBytes;
        //有序列号
        if ((flag & 0x0100) == 0x0100) {
            headerBytes = Arrays.copyOfRange(message, 0, AccessHeaderWithSeqNum.WITH_SEQ_NUM_HEADER_SIZE);
        } else {
            headerBytes = Arrays.copyOfRange(message, 0, AccessFixedHeader.WITHOUT_SEQ_NUM_HEADER_SIZE);
        }


        if (logger.isTraceEnabled() && isDebugEnabled) {
            logger.trace("header raw bytes \r\n{}",
                    ByteUtils.bytesAsHexString(headerBytes, dumpBytes));
        }

        AccessFixedHeader fixedHdr = AccessSignalCodecUtils.decodeAccessHeader(headerBytes, esbBeanCodec);

        if (null == fixedHdr) {
            logger.error(" Can't decode esb's fixed header");
            ctx.channel().close();
            return;
        }

        if (logger.isTraceEnabled() && isDebugEnabled) {
            logger.trace("fixed header {}", fixedHdr);
        }

        byte[] bytes = Arrays.copyOfRange(message, headerBytes.length, message.length);

        if (logger.isTraceEnabled() && isDebugEnabled) {
            logger.trace("body raw bytes \r\n{}",
                    ByteUtils.bytesAsHexString(bytes, dumpBytes));
        }

        EsbAccess2TerminalSignal signal = AccessSignalCodecUtils.decodeSignalBody(
                fixedHdr, bytes, esbBeanCodec, esbTypeMetainfo);

        if (null != signal) {
            if (logger.isDebugEnabled() && isDebugEnabled) {
                logger.debug("channel {} \r\nrecv signal {}", ctx.channel(), signal);
            }
            out.add(signal);
        } else {
            if (logger.isTraceEnabled() && isDebugEnabled) {
                logger.trace("can't decode esb signal body, " +
                        "maybe unknow protocol code {} \r\n", fixedHdr.getMsgCode());
            }
        }
    }

    /**
     * @param dumpBytes the dumpBytes to set
     */
    public void setDumpBytes(int dumpBytes) {
        this.dumpBytes = dumpBytes;
    }

    public int getDumpBytes() {
        return dumpBytes;
    }

    public boolean isDebugEnabled() {
        return isDebugEnabled;
    }

    public void setDebugEnabled(boolean isDebugEnabled) {
        this.isDebugEnabled = isDebugEnabled;
    }

    /**
     * @return the esbBeanCodec
     */
    public BeanFieldCodec getEsbBeanCodec() {
        return esbBeanCodec;
    }

    /**
     * @param esbBeanCodec the esbBeanCodec to set
     */
    public void setEsbBeanCodec(BeanFieldCodec esbBeanCodec) {
        this.esbBeanCodec = esbBeanCodec;
    }

    /**
     * @return the esbTypeMetainfo
     */
    public Int2TypeMetainfo getEsbTypeMetainfo() {
        return esbTypeMetainfo;
    }

    /**
     * @param esbTypeMetainfo the esbTypeMetainfo to set
     */
    public void setEsbTypeMetainfo(Int2TypeMetainfo esbTypeMetainfo) {
        this.esbTypeMetainfo = esbTypeMetainfo;
    }

    /**
     * @return the maxMessageLength
     */
    public int getMaxMessageLength() {
        return maxMessageLength;
    }

    /**
     * @param maxMessageLength the maxMessageLength to set
     */
    public void setMaxMessageLength(int maxMessageLength) {
        this.maxMessageLength = maxMessageLength;
    }

    public void setEncryptKey(int encryptKey) {
        this.encryptKey = encryptKey;
    }
}
